package com.sparkseries.common.log.aspect;

import com.sparkseries.common.log.annotation.SLog;
import com.sparkseries.common.log.domain.BaseLog;
import com.sparkseries.common.log.enums.LogStatusEnums;
import com.sparkseries.common.log.logProducer.LogProducer;
import com.sparkseries.common.log.utils.RemoteAddressUtils;
import com.sparkseries.common.log.utils.ServerAddressUtils;
import com.sparkseries.common.security.domain.SecurityUser;
import com.sparkseries.common.util.domain.Result;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpHeaders;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.slf4j.event.Level;

import java.lang.reflect.Method;
import java.util.Date;

/**
 * 日志切面处理类，主要功能是拦截带有 @SLog 注解的方法，
 */
@Slf4j
@Aspect
@Component
public class LogAspect {
    // 日志生产者，负责将封装好的 BaseLog 对象保存到指定的存储位置
    @Resource
    private LogProducer logProducer;

    // Spring 环境配置对象，用于获取服务器的相关配置信息
    @Resource
    private Environment environment;

    /**
     * 环绕通知，拦截带有 @SLog 注解的方法。
     * 该方法会记录方法执行的开始时间，执行目标方法，并捕获可能抛出的异常，
     * 最后计算方法的执行时间，并调用 saveLog 方法保存日志信息。
     *
     * @param point 连接点对象，包含了被拦截方法的相关信息
     * @return 目标方法的返回值
     * @throws Throwable 目标方法可能抛出的异常
     */
    @Around("@annotation(com.sparkseries.common.log.annotation.SLog)")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long startTime = System.currentTimeMillis();
        Result<?> result = null;
        Throwable exception = null;
        try {
            result = (Result<?>) point.proceed();
        } catch (Throwable e) {
            exception = e;
            throw e;
        } finally {
            // 计算目标方法的执行时间
            long executionTime = System.currentTimeMillis() - startTime;
            saveLog(point, executionTime, exception, result);
        }
        return result;
    }

    /**
     * 保存日志信息的方法，将捕获到的方法执行信息封装到 BaseLog 对象中，
     * 并调用 LogProducer 的 saveLog 方法将日志保存。
     *
     * @param point       连接点对象，包含了被拦截方法的相关信息
     * @param time        目标方法的执行时间
     * @param exception   目标方法抛出的异常，如果没有异常则为 null
     * @param result      目标方法的返回结果，如果没有返回结果则为 null
     */
    private void saveLog(ProceedingJoinPoint point, long time, Throwable exception, Result<?> result) {
        // 获取方法签名
        MethodSignature signature = (MethodSignature) point.getSignature();
        // 获取被拦截的方法对象
        Method method = signature.getMethod();
        BaseLog baseLog = new BaseLog();

        // 获取方法上的 @SLog 注解
        SLog annotation = method.getAnnotation(SLog.class);
        if (annotation != null) {
            baseLog.setLogOperation(annotation.operation());
            baseLog.setLogType(annotation.type());
        }

        // 获取当前登录用户信息
        SecurityUser sysUser = getCurrentUser();
        if (sysUser != null) {
            baseLog.setCreateBy(sysUser.getUsername());
        }

        baseLog.setCreateDate(new Date());
        baseLog.setRequestTime((int) time);

        // 获取当前的 HttpServletRequest 对象
        HttpServletRequest request = getCurrentRequest();
        if (request != null) {
            baseLog.setRequestMethod(request.getMethod());
            baseLog.setUserAgent(request.getHeader(HttpHeaders.USER_AGENT));
            baseLog.setRequestUri(request.getRequestURI());
            baseLog.setRemoteAddr(RemoteAddressUtils.getIpAddr(request));
            baseLog.setServerAddr(ServerAddressUtils.getServerBaseUrl(environment));
        }

        setRequestParams(baseLog, point);

        if (exception != null) {
            baseLog.setErrorInfo(getSecondLineUsingSplit(exception.getMessage()));
            baseLog.setLogLevel(Level.ERROR.name());
        } else {
            baseLog.setLogLevel(Level.INFO.name());
        }

        if (result != null) {
            if (result.getCode() == 200) {
                baseLog.setStatus(LogStatusEnums.SUCCESS.value());
            } else {
                baseLog.setStatus(LogStatusEnums.FAILURE.value());
            }
            baseLog.setInfo(result.getMsg());
        }

        try {
            logProducer.saveLog(baseLog);
        } catch (Exception e) {
            log.error("Failed to save log: {}", baseLog, e);
        }
    }

    /**
     * 从输入字符串中提取第二行内容。
     * 如果输入字符串为空或没有第二行，则返回空字符串。
     *
     * @param input 输入的字符串
     * @return 输入字符串的第二行内容，如果不存在则返回空字符串
     */
    public static String getSecondLineUsingSplit(String input) {
        if (input == null || input.isEmpty()) {
            return "";
        }
        // 按换行符分割字符串
        String[] lines = input.split("\\r?\\n");
        // 如果存在第二行，则返回第二行内容，否则返回空字符串
        return lines.length > 1 ? lines[1] : "";
    }

    /**
     * 获取当前登录的用户信息。
     * 从 Spring Security 的上下文信息中获取当前用户的认证信息。
     *
     * @return 当前登录的用户信息，如果获取失败则返回 null
     */
    private SecurityUser getCurrentUser() {
        try {
            // 从 Spring Security 上下文获取认证信息，并提取用户主体
            return (SecurityUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        } catch (Exception e) {
            // 若获取用户信息过程中发生异常，记录警告日志
            log.warn("Failed to get current user", e);
            return null;
        }
    }

    /**
     * 获取当前的 HttpServletRequest 对象。
     * 从 Spring 的请求上下文信息中获取当前请求的 ServletRequestAttributes，
     * 进而获取 HttpServletRequest 对象。
     *
     * @return 当前的 HttpServletRequest 对象，如果获取失败则返回 null
     */
    private HttpServletRequest getCurrentRequest() {
        // 获取当前请求的属性信息
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 判断请求属性信息是否为 ServletRequestAttributes 类型
        return requestAttributes instanceof ServletRequestAttributes
                // 若是，则获取 HttpServletRequest 对象
                ? ((ServletRequestAttributes) requestAttributes).getRequest()
                // 否则返回 null
                : null;
    }

    /**
     * 设置请求参数信息到 BaseLog 对象中。
     * 将被拦截方法的参数信息添加到 BaseLog 对象的请求参数字段中。
     *
     * @param baseLog BaseLog 对象，用于封装日志信息
     * @param point  连接点对象，包含了被拦截方法的相关信息
     */
    private void setRequestParams(BaseLog baseLog, ProceedingJoinPoint point) {
        // 获取被拦截方法的参数数组
        Object[] args = point.getArgs();
        if (args != null && args.length > 0) {
            try {
                for (Object arg : args) {
                    // 将参数信息添加到 BaseLog 对象的请求参数字段中
                    baseLog.setRequestParams(arg + "☆");
                }
            } catch (Exception e) {
                // 若转换参数信息过程中发生异常，记录错误日志，并设置请求参数为转换失败
                log.error("Failed to convert request parameters to JSON", e);
                baseLog.setRequestParams("Conversion failed");
            }
        }
    }
}